This 完全取決於呼叫地點。
就是函式被呼叫的地方。
function foo() {
console.log( "foo" );
bar(); // bar 的呼叫地點
}
function bar() {
console.log( "bar" );
baz(); // baz 的呼叫地點
}
function baz() {
console.log( "baz" );
}
foo(); // foo 的呼叫地點
接著,什麼是呼叫堆疊?
Tony 用的是 chrom 的 dev tool 裡面的 performance。開啟這個腳本後,錄製。
在呼叫 baz 的過程中,有經過 foo 與 bar。也可以看出範疇存活的狀態。
this 總共有四個規則,來決定指向的對象。
當然衝突的時候,也會有優先順序的問題。
就是沒有規則時,指向的位置。
function foo() {
var a = 3;
console.log( this.a );
}
var a = 2;
foo(); // 2
他會指向全域的 2
,而不是自身或範疇中的 3
。
但如果使用了嚴格模式。
function foo() {
"use strict";
var a = 3;
console.log( this.a );
}
var a = 2;
foo(); // undefined
( 咦?還以為是 3
,以為嚴格會包覆範疇)
其實是 this 找不到。
function foo() {
"use strict";
console.log( this, "haha" );
}
foo(); // undefined "haha"
但是嚴格模式,和他呼叫的地方沒有關係。
function foo(){
console.log( this.a );
}
var a = 2;
(function(){
"use strict"
foo(); // 2
})();
如果呼叫地點是在物件裡面。稱為
function foo(){
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
這是在物件 obj 裡面呼叫,所以指向物件本身。
可是其實 obj 不包含 foo 函式。
是屬性新增的參考,所以擁有物件和包含物件的稱呼並不恰當。
讓我們來更釐清一下。
function bar() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: bar // 釐清用 可以直接用 foo
}
var obj1 = {
a: 2,
obj: obj2 // 釐清用 obj 可以直接用 obj2
}
obj1.obj.foo(); // 42
執行的位置在 obj2 裡面的 foo。
就是 this 失去繫結,偷偷回到預設全域的狀態。
你只需要注意函式在哪裡執行就好,讓我們找出 ()
吧。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo; // bar 其實就是 foo 了。( 參考 foo )
var a = "Global";
bar(); // "Global"
這時候的 bar 與 obj 沒有關係。
( 有點閉包移除環境的感覺 )
所以執行 bar 就會直接使用預設繫結。
function foo() {
console.log( this.a );
}
function doFoo(fn) {
fn();
}
var obj = {
a: 2,
foo: foo
}
var a = "Global";
doFoo( obj.foo );
聰明如你,一定猜的到是 "Global"。但為什麼呢?
只要看呼叫函式的地點就可以了。
就在 doFoo
裡面的 fn()
。這時候的 this 就會指向預設的地方。
最後還有 setTimeout 的使用,也會讓 callback 延後執行。
(總總以上的三項,都有相當類似 closure 讓環境消失的做法)
這種似曾相似感,在先前的強制轉型有看過。
明確的繫結,就是由你指定 this 繫結的對象。
藉由函式的 prototype 裡面的 call(..) 和 apply(..) 來達成。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
硬繫結,藉由明確的繫結,建立無可替代的繫結。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
var bar = function() {
foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100); // 2
bar.call( window ); // 2
不管如何調用 bar,他都會永遠以 obj 為最後的 this。因為裡面的 call 已經指定好了。
書上還有提供許多厲害的硬繫結。這邊先略過。
但每個都超有趣的!
new 是運算式。在使用時,會做三件事
就是函式前面加個 new,所以只有函式不回傳東西,JS 引擎才會幫你生出一個物件。
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
因為 foo() 沒有回傳值,就建了一個 bar 的物件,他的屬性有 a: 2。
當然 this 就會指向 bar 了。
來排優先順序吧! 到底 this 歸誰呢?
未看先猜,預設繫結是撿剩的。
明天見!